#include <glib.h>
#include <gtk/gtk.h>
#include <SDL.h>
#include "racanhack.h"
#include "task.h"
#include "video.h"
#include "cave.h"
#include "player.h"
#include "monster.h"
#include "monster2.h"
#include "object.h"
#include "object2.h"
#include "floor.h"
#include "los.h"
#include "main.h"

enum {
  MONSTER_AI_MOVE,
  MONSTER_AI_ATTACK
};

void monster_trans_state(state (*fp)(struct _task *task), struct _monster *monster)
{
  if (monster->task != NULL) monster->task->alive = FALSE;
  monster->task = task_add(fp, monster);
}
 
void monster_player_trans_state(state (*fp)(struct _task *task), struct _monster *monster, struct _player *player)
{
  struct _task *task;
  if (monster->task != NULL) monster->task->alive = FALSE;
  if (player->task != NULL) player->task->alive = FALSE;
  task = task_add(fp, monster, player);
  monster->task = task;
  player->task = task;
}

struct _monster *is_monster(struct _coord coord)
{
  GList *li;
  struct _monster *monster;
  for (li = g_list_first(curf->monster_list); li != NULL; li = g_list_next(li)) {
    monster = (struct _monster *)li->data;
    if ((monster->coord.x == coord.x) && (monster->coord.y == coord.y)) return monster;
  };
  return NULL;
}

void monster_take_hit(struct _monster *monster, guint damage)
{
  monster->damage = CLAMP(monster->damage + damage, 0, G_MAXUINT);
}

int monster_AI(struct _monster *monster)
{
  int i;
  if (is_los_onsight(monster->coord)) {
    for (i = 0; i < 8; i++) {
      if (is_player(coord_dd(monster->coord, ddd(i)))) {
	return MONSTER_AI_ATTACK;
      };
    };
  };
  return MONSTER_AI_MOVE;
};

direction monster_AI_move(struct _monster *monster)
{
  int i;
  if (monster->id == MONSTER_ID_BAT) return ddd(g_random_int_range(0, 8));
  if (is_los_onsight(monster->coord)) {
    int dist;
    int min_i = 10000;
    int min_dist = 10000;
    for (i = 0; i < 8; i++) {
      if (is_passable(monster->coord, ddd(i)) == FALSE) continue;
      dist = distance(coord_dd(monster->coord, ddd(i)), Player.coord);
      if (dist < min_dist) {
	min_dist = dist;
	min_i = i;
      };
    };
    if (min_dist != 10000) return ddd(min_i);
  } else {
    if (is_map_waynavi(monster->coord)) {
      direction dir;
      for (i = 0; i < 4; i++) {
	dir = lefthand(i, monster->dir);
	if (is_passable(monster->coord, dir) == FALSE) continue;
	if (is_map_waynavi(coord_dd(monster->coord, dir))) return dir;
      };
    } else {
      for (i = 0; i < 8; i++) {
	if (is_passable(monster->coord, ddd(i)) == FALSE) continue;
	if (is_map_waynavi(coord_dd(monster->coord, ddd(i)))) return ddd(i);
      };
    };
  };
  i = g_random_int_range(0, 8);
  return ddd(i);
}

direction monster_AI_attack(struct _monster *monster)
{
  int i;
  for (i = 0; i < 8; i++) {
    if (is_player(coord_dd(monster->coord, ddd(i)))) {
      return ddd(i);
    };
  };
  return DIR_NULL;
};

state monster_turn_start(struct _task *task)
{
  struct _monster *monster;
  int result;
  direction result_dir;
  monster = (struct _monster *)task->pointer[0];
  monster->sprite->x = monster->coord.x * SPRITE_W;
  monster->sprite->y = monster->coord.y * SPRITE_H;
  switch (monster->status.type) {
  case STATUS_NORMAL:
    result = monster_AI(monster); 
    switch (result) {
    case MONSTER_AI_MOVE:
      result_dir = monster_AI_move(monster);
      if (result_dir != DIR_NULL) {
	monster->dir = result_dir;
	monster_trans_state(monster_move, monster);
	return;
      } else {
	monster_trans_state(monster_turn_end, monster);
	return;
      };
      break;
    case MONSTER_AI_ATTACK:
      result_dir = monster_AI_attack(monster);
      if (result_dir != DIR_NULL) {
	monster->dir = result_dir;
	monster_trans_state(monster_turn_wait_attack, monster);
	return;
      } else {
	monster_trans_state(monster_turn_end, monster);
	return;
      };
      break;
    };
    break;
  case STATUS_POISON:
    monster->status.time--;
    if (monster->status.time <= 0) {
      monster->status.type = STATUS_NORMAL;
      monster->status.time = 0;
    };
    result_dir = ddd(g_random_int_range(0, 8));
    monster->dir = result_dir;
    monster_trans_state(monster_move, monster);
    return;
    break;
  case STATUS_SLEEP:
    monster->status.time--;
    if (monster->status.time <= 0) {
      monster->status.type = STATUS_NORMAL;
      monster->status.time = 0;
    };
    monster_trans_state(monster_turn_end, monster);
    return;
    break;
  case STATUS_BLIND:
  case STATUS_CONFUSE:
    monster->status.time--;
    if (monster->status.time <= 0) {
      monster->status.type = STATUS_NORMAL;
      monster->status.time = 0;
    };
    result_dir = ddd(g_random_int_range(0, 8));
    monster->dir = result_dir;
    monster_trans_state(monster_move, monster);
    return;
    break;
  case STATUS_SLEEP_MONSTERHOUSE:
    monster_trans_state(monster_turn_end, monster);
    return;
    break;
  };
  g_assert_not_reached();
}

state monster_move(struct _task *task)
{
  struct _monster *monster;
  monster = (struct _monster *)task->pointer[0];
  if (is_passable(monster->coord, monster->dir) == FALSE) {
    monster_trans_state(monster_turn_end, monster);
    return;
  };
  monster->coord = coord_dd(monster->coord, monster->dir);
  task_add(move_monster_anime, monster);
  monster_trans_state(monster_turn_end, monster);
  return;
}

state move_monster_anime(struct _task *task)
{
  struct _monster *monster;
  monster = (struct _monster *)task->pointer[0];
  if (is_los_onsight(monster->coord)) {
    monster->sprite->x += (SPRITE_W / MOTION) * ddx(monster->dir);
    monster->sprite->y += (SPRITE_H / MOTION) * ddy(monster->dir);
  };
  if (motion_end(task)) {
    monster->sprite->x = monster->coord.x * SPRITE_W;
    monster->sprite->y = monster->coord.y * SPRITE_H;
    task->alive = FALSE;
    return;
  };
}

state monster_turn_wait_attack(struct _task *task)
{
  struct _monster *monster;
  monster = (struct _monster *)task->pointer[0];
  if (is_los_onsight(monster->coord)) {
    monster->sprite->surface = surface_monster_idle[monster->id][monster->dir][animecount(task)];
  } else {
    monster->sprite->surface = NULL;
  };
}

state monster_turn_attack(struct _task *task)
{
  struct _monster *monster;
  struct _player *player;
  monster = (struct _monster *)task->pointer[0];
  player = is_player(coord_dd(monster->coord, monster->dir));
  if (player != NULL) {
    monster_player_trans_state(monster_attack_player, monster, player);
    return;
  } else {
    monster_trans_state(monster_turn_end, monster);
    return;
  };
}

state monster_attack_player(struct _task *task)
{
  enum {BUF_LENGTH = 32};
  struct _monster *monster;
  struct _player *player;
  monster = (struct _monster *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  monster->sprite->surface = surface_monster_attack[monster->id][monster->dir][animecount(task)];
  player->sprite->surface = surface_player_damage[player->dir][animecount(task)];
  if (task->count == 0) {
    int d;
    gchar buf[BUF_LENGTH]; 
    gboolean damageflag = FALSE;
    switch(monster_act(monster)) {
    case MONSTER_ACT_BITE:
      g_snprintf(buf, BUF_LENGTH, "bites you.");
      damageflag = TRUE;
      break;
    case MONSTER_ACT_HIT:
      g_snprintf(buf, BUF_LENGTH, "hits you.");
      damageflag = TRUE;
      break;
    case MONSTER_ACT_CRAWL:
      g_snprintf(buf, BUF_LENGTH, "crawls you.");
      damageflag = TRUE;
      break;
    case MONSTER_ACT_CAST_SLEEP:
      g_snprintf(buf, BUF_LENGTH, "get you slept.");
      if (player->status.type != STATUS_SLEEP) {
	player->status.type = STATUS_SLEEP;
	player->status.time = g_random_int_range(1, 5);
      };
      break;
    case MONSTER_ACT_CAST_POISON:
      g_snprintf(buf, BUF_LENGTH, "get you poisoned.");
      if (player->status.type != STATUS_POISON) {
	player->status.type = STATUS_POISON;
	player->status.time = 25;
      };
      break;
    case MONSTER_ACT_RUST_SHIELD:
      if (player->shield != &Noshield) {
	g_snprintf(buf, BUF_LENGTH, "rust your shield.");
	player->shield->plus = CLAMP(object_ac(player->shield) - 1, 0, G_MAXINT);
      } else {
	g_snprintf(buf, BUF_LENGTH, "gaze at you.");
      };
      break;
    case MONSTER_ACT_STEAL:
      if (g_list_length(player->inventory_list) > 0) {
	struct _object *object;
	int which;
	which = g_random_int_range(0, g_list_length(player->inventory_list));
	object = (struct _object *)g_list_nth_data(player->inventory_list, which);
	if ((object == player->weapon) ||
	    (object == player->shield) ||
	    (object == player->arrow) ||
	    (object == player->ring)) {
	  takeoff(object, player);
	};
	object_move_from_inventory(object);
	object->coord = monster->coord;
	object_trans_state(object_on_floor, object);
	g_snprintf(buf, BUF_LENGTH, "stealed %s.", object_name(object));
      } else {
	g_snprintf(buf, BUF_LENGTH, "gaze at you.");
      };
      break;
    case MONSTER_ACT_CAST_TELEPORT:
      g_snprintf(buf, BUF_LENGTH, "get you teleported.");
      player->coord = rand_coord();
      player->sprite->x = player->coord.x * SPRITE_W;
      player->sprite->y = player->coord.y * SPRITE_H;
      video_fade();
      break;
    };
    msg("%s %s", monster_name(monster), buf);
    if (damageflag) {
      d = damage_calc_attacked_by_monster(monster, player->shield, player);
      if (d == DODGED_DAMAGE) {
	msg("you dodged.");
      } else {
	take_hit(player, d, "%s %s", monster_name(monster), buf);
      };
    };
  };
  if (motion_end(task)) {
    player->dir = opposite(monster->dir);
    monster_trans_state(monster_turn_end, monster);
    player_trans_state(player_action_end, player);
    return;
  };
}

state monster_turn_end(struct _task *task)
{
  struct _monster *monster;
  monster = (struct _monster *)task->pointer[0];
  if (is_los_onsight(monster->coord)) {
    monster->sprite->surface = surface_monster_idle[monster->id][monster->dir][animecount(task)];
  } else {
    monster->sprite->surface = NULL;
  };
}

state monster_dodge_player(struct _task *task)
{
  struct _monster *monster;
  struct _player *player;
  monster = (struct _monster *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  monster->sprite->surface = surface_monster_idle[monster->id][monster->dir][animecount(task)];
  if (task->count == 0) {
    msg("%s dodged your attack.", monster_name(monster));
    monster->damage = 0;
  };
  if (motion_end(task)) {
    monster_trans_state(monster_turn_end, monster);
    player_trans_state(player_action_end, player);
    return;
  };
}

state monster_damage_by_player(struct _task *task)
{
  struct _monster *monster;
  struct _player *player;
  monster = (struct _monster *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  monster->sprite->surface = surface_monster_damage[monster->id][monster->dir][animecount(task)];
  if (task->count == 0) {
    msg("%s got %d damage.", monster_name(monster), monster->damage);
    monster->chp -= monster->damage;
    monster->damage = 0;
    if (monster->status.type == STATUS_SLEEP) {
      monster->status.type = STATUS_NORMAL;
      monster->status.time = 0;
    };
    if (monster->status.type == STATUS_SLEEP_MONSTERHOUSE) {
      monster->status.type = STATUS_NORMAL;
      monster->status.time = 0;
    };
    if (monster->chp <= 0) {
      monster_player_trans_state(monster_killed_by_player, monster, player);
      return;
    };
  };
  if (motion_end(task)) {
    monster_trans_state(monster_turn_end, monster);
    player_trans_state(player_action_end, player);
    return;
  };
}

state monster_killed_by_player(struct _task *task)
{
  struct _monster *monster;
  struct _player *player;
  monster = (struct _monster *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  monster->sprite->surface = surface_monster_idle[monster->id][monster->dir][animecount(task)];
  if (task->count == 0) {
    msg("You killed %s!", monster_name(monster));
    msg("You got %d exp.", monster_exp(monster));
    player->exp += monster_exp(monster);
  };
  if (motion_end(task)) {
    monster_trans_state(monster_delete, monster);
    player_trans_state(player_action_end, player);
    return;
  };
}

state monster_delete(struct _task *task)
{
  struct _monster *monster;
  monster = (struct _monster *)task->pointer[0];
  curf->monster_list = g_list_remove(curf->monster_list, monster);
  monster->sprite->alive = FALSE;
  monster->sprite = NULL;
  task->alive = FALSE;
  my_free(monster);
  return;
}
